04.1 精通自定义 View 之属性动画进阶——PropertyValuesHolder 与 Keyframe

返回自定义 View 目录

ValueAnimator、ObjectAnimator 除了 ofInt()、ofFloat()、ofObject() 函数创建 Animator 实例的方法以外,都还有一个方法:

1
2
3
4
5
6
// valueAnimator 的
public static ValueAnimator ofPropertyValuesHolder (
PropertyValuesHolder... values)
// ObjectAnimator的
public static ObjectAnimator ofPropertyValuesHolder (
Object target,PropertyValuesHolder... values)

4.1.1 PropertyValuesHolder

1. 概述

PropertyValuesHolder 这个类的意义就是,它其中保存了动画过程中所需要操作的属性和对应的值。我们通过 ofFloat(Object target, String propertyName, float… values) 构造的动画,ofFloat() 的内部实现其实就是将传进来的参数封装成 PropertyValuesHolder 实例来保存动画状态。在封装成 PropertyValuesHolder 实例以后,后期的各种操作也是以 PropertyValuesHolder 为主的。

PropertyValuesHolder 中有很多函数,有些函数的 api 等级是11,有些函数的 api 等级是 14 和 21,具体参考文档《Google:PropertyValuesHolder》

首先,我们来看看创建实例的函数:

1
2
3
4
public static PropertyValuesHolder ofFloat(String propertyName, float... values)
public static PropertyValuesHolder ofInt(String propertyName, int... values)
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, Object... values)
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)

2. PropertyValuesHolder 之 ofFloat()、ofInt()

1)ofFloat()、ofInt()

1
2
public static PropertyValuesHolder ofFloat(String propertyName, float... values)
public static PropertyValuesHolder ofInt(String propertyName, int... values)

  • propertyName:表示 ObjectAnimator 需要操作的属性名。即 ObjectAnimator 需要通过反射查找对应属性的 setProperty() 函数的那个 property。
  • values:属性所对应的参数,同样是可变长参数,可以指定多个,还记得我们在 ObjectAnimator 中讲过,如果只指定了一个,那么 ObjectAnimator 会通过查找 getProperty() 方法来获得初始值。

2)ObjectAnimator.ofPropertyValuesHolder()
ObjectAnimator 提供了一个方法,构造 PropertyValuesHolder 来构造动画。

1
2
public static ObjectAnimator ofPropertyValuesHolder(Object target,
PropertyValuesHolder... values)

  • target:指需要执行动画的控件。
  • values:是一个可变长参数,可以传进去多个PropertyValuesHolder 实例,由于每个 PropertyValuesHolder 实例都会针对一个属性做动画,所以如果传进去多个 PropertyValuesHolder 实例,将会对控件的多个属性同时做动画操作。

3)示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainActivity extends AppCompatActivity {
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
Button startBtn = findViewById(R.id.start_btn);
mTv = findViewById(R.id.tv);
startBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAnim();
}
});
}
private void startAnim() {
PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("rotation",
60f, -60f, 40f, -40f, 20f, -20f, 10f, -10f, 0f);
PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofFloat("alpha",
0.1f, 1f, 0.1f, 1f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTv, rotationHolder, alphaHolder);
animator.setDuration(3000);
animator.start();
}
}

3. PropertyValuesHolder 之 ofObject()

1)概述

1
2
public static PropertyValuesHolder ofObject(String propertyName,
TypeEvaluator evaluator, Object... values)

  • propertyName:ObjectAnimator 动画操作的属性名。
  • evaluator:Evaluator 实例,Evaluator 是将当前动画进度计算出当前值的类,可以使用系统自带的 IntEvaluator、FloatEvaluator 也可以自定义。
  • values:可变长参数,表示操作动画属性的值。

2)示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MainActivity extends AppCompatActivity {
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
Button startBtn = findViewById(R.id.start_btn);
mTv = findViewById(R.id.tv);
startBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAnim();
}
});
}
private void startAnim() {
PropertyValuesHolder charHolder = PropertyValuesHolder.ofObject("CharText",
new CharEvaluator(), Character.valueOf('A'), Character.valueOf('Z'));
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTv, charHolder);
animator.setDuration(3000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
}
}

自定义 View 和 Evaluator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyTextView extends AppCompatTextView {
private Character charText;
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setCharText(Character charText) {
setText(String.valueOf(charText));
}
}
public class CharEvaluator implements TypeEvaluator<Character> {
@Override
public Character evaluate(float fraction, Character startValue, Character endValue) {
int startInt = (int) startValue;
int endInt = (int) endValue;
int result = (int) (startInt + fraction * (endInt - startInt));
return (char) result;
}
}

4.1.2 Keyframe

1. 概述

如果要控制动画速率的变化,可以通过自定义插值器,也可以通过自定义 Evaluator 来实现。但需要足够的数学知识。 为了解决方便的控制动画速率的问题,谷歌为了我们定义了一个 KeyFrame 的类,KeyFrame 直译过来就是关键帧。

1
public static Keyframe ofFloat(float fraction, float value)

  • fraction:表示当前的显示进度,即从加速器中 getInterpolation() 函数的返回值。
  • value:表示当前应该在的位置。

比如 Keyframe.ofFloat(0, 0) 表示动画进度为 0 时,动画所在的数值位置为 0;Keyframe.ofFloat(0.25f, -20f) 表示动画进度为 25% 时,动画所在的数值位置为 -20;Keyframe.ofFloat(1f, 0) 表示动画结束时,动画所在的数值位置为 0。

PropertyValuesHolder 是这样使用 KeyFrame 对象的:

1
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)

  • propertyName:动画所要操作的属性名。
  • values:Keyframe 的列表,PropertyValuesHolder 会根据每个 Keyframe 的设定,定时将指定的值输出给动画。

所以完整的 KeyFrame 的使用代码应该是这样的:

1
2
3
4
5
6
7
8
Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(1, 0);
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe(
"rotation",frame0,frame1,frame2);
Animator animator = ObjectAnimator.ofPropertyValuesHolder(mImage,frameHolder);
animator.setDuration(1000);
animator.start();

2. 示例

1)布局文件 act_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<Button
android:id="@+id/start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start animation"/>
<ImageView
android:id="@+id/tel_img"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_margin="30dp"
android:src="@drawable/icon_tel"/>
</LinearLayout>

2)MainActivity.java 核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(0.2f, 20f);
Keyframe frame3 = Keyframe.ofFloat(0.3f, -20f);
Keyframe frame4 = Keyframe.ofFloat(0.4f, 20f);
Keyframe frame5 = Keyframe.ofFloat(0.5f, -20f);
Keyframe frame6 = Keyframe.ofFloat(0.6f, 20f);
Keyframe frame7 = Keyframe.ofFloat(0.7f, -20f);
Keyframe frame8 = Keyframe.ofFloat(0.8f, 20f);
Keyframe frame9 = Keyframe.ofFloat(0.9f, -20f);
Keyframe frame10 = Keyframe.ofFloat(1f, 0);
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("rotation",
frame0, frame1, frame2, frame3, frame4, frame5,
frame6, frame7, frame8, frame9, frame10);
Animator animator = ObjectAnimator.ofPropertyValuesHolder(mView, frameHolder);
animator.setDuration(2000);
animator.start();

3. ofInt 和 ofFloat

1
2
3
4
5
6
// ofFloat
public static Keyframe ofFloat(float fraction)
public static Keyframe ofFloat(float fraction, float value)
// ofInt
public static Keyframe ofInt(float fraction)
public static Keyframe ofInt(float fraction, int value)

fraction 表示当前关键帧所在的动画进度位置,value 表示当前位置所对应的值。

Keyframe 还有一些常用函数来设置 fraction,value 和 interpolator,定义如下:

1
2
3
4
5
6
// 设置fraction参数,即Keyframe所对应的进度
public void setFraction(float fraction)
// 设置当前Keyframe所对应的值
public void setValue(Object value)
// 设置Keyframe动作期间所对应的插值器
public void setInterpolator(TimeInterpolator interpolator)

如果使用 ofFloat(float fraction) 来构造,也必须使用 setValue(Object value) 来设置这个关键帧所对应的值。

4. 插值器

如果给某个 Keyframe 设置上插值器,那么这个插值器就是从上一个 Keyframe 开始到当前设置插值器的 Keyframe 时,这个过程值的计算是利用这个插值器的,比如:

1
2
3
4
5
Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
frame1.setInterpolator(new BounceInterpolator());
Keyframe frame2 = Keyframe.ofFloat(1f, 20f);
frame2.setInterpolator(new LinearInterpolator());

在上面的代码中,我们给 frame1 设置了插值器 BounceInterpolator,那么在 frame0 到 frame1 的中间值计算过程中,就是用的就是回弹插值器;同样,我们给 frame2 设置了线性插值器 LinearInterpolator,所以在 frame1 到 frame2 的中间值计算过程中,使用的就是线性插值器。很显然,给 Keyframe.ofFloat(0f, 0) 设置插值器是无效的,因为它是第一帧。

5. Keyframe 之 ofObject

1
2
public static Keyframe ofObject(float fraction)
public static Keyframe ofObject(float fraction, Object value)

同样,如果使用 ofObject(float fraction) 来构造,也必须使用 setValue(Object value) 来设置这个关键帧所对应的值。

还以 TextView 更改字母的例子来使用下 Keyframe.ofObject:

1
2
3
4
5
6
7
8
9
Keyframe frame0 = Keyframe.ofObject(0f, Character.valueOf('A'));
Keyframe frame1 = Keyframe.ofObject(0.1f, Character.valueOf('L'));
Keyframe frame2 = Keyframe.ofObject(1f, Character.valueOf('Z'));
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("CharText",
frame0, frame1, frame2);
frameHolder.setEvaluator(new CharEvaluator());
Animator animator = ObjectAnimator.ofPropertyValuesHolder(mView, frameHolder);
animator.setDuration(2000);
animator.start();

6. 疑问:如果没有设置进度为0或者进度为1时的关键帧,展示是怎样的?

  • 如果去掉第 0 帧,将以第一个关键帧为起始位置。
  • 如果去掉结束帧,将以最后一个关键帧为结束位置。
  • 使用 Keyframe 来构建动画,至少要有两个或两个以上帧,否则崩溃。

4.1.3 PropertyValuesHolder 其他函数

PropertyValuesHolder 除了上面的讲到的 ofInt、ofFloat、ofObject、ofKeyframe 以外,API 11 的还有几个函数:

1
2
3
4
5
6
7
8
9
10
11
12
// 设置动画的Evaluator
public void setEvaluator(TypeEvaluator evaluator)
// 用于设置ofFloat所对应的动画值列表
public void setFloatValues(float... values)
// 用于设置ofInt所对应的动画值列表
public void setIntValues(int... values)
// 用于设置ofKeyframe所对应的动画值列表
public void setKeyframes(Keyframe... values)
// 用于设置ofObject所对应的动画值列表
public void setObjectValues(Object... values)
// 设置动画属性名
public void setPropertyName(String propertyName)

4.1.4 示例:电话响铃效果

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 左右晃动
Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(0.2f, 20f);
Keyframe frame3 = Keyframe.ofFloat(0.3f, -20f);
Keyframe frame4 = Keyframe.ofFloat(0.4f, 20f);
Keyframe frame5 = Keyframe.ofFloat(0.5f, -20f);
Keyframe frame6 = Keyframe.ofFloat(0.6f, 20f);
Keyframe frame7 = Keyframe.ofFloat(0.7f, -20f);
Keyframe frame8 = Keyframe.ofFloat(0.8f, 20f);
Keyframe frame9 = Keyframe.ofFloat(0.9f, -20f);
Keyframe frame10 = Keyframe.ofFloat(1f, 0);
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("rotation",
frame0, frame1, frame2, frame3, frame4, frame5,
frame6, frame7, frame8, frame9, frame10);
// X 轴缩放
Keyframe scaleXFrame0 = Keyframe.ofFloat(0f, 1);
Keyframe scaleXFrame1 = Keyframe.ofFloat(0.1f, 1.2f);
Keyframe scaleXFrame9 = Keyframe.ofFloat(0.9f, 1.2f);
Keyframe scaleXFrame10 = Keyframe.ofFloat(1f, 1);
PropertyValuesHolder scaleXholder = PropertyValuesHolder.ofKeyframe("scaleX",
scaleXFrame0, scaleXFrame1, scaleXFrame9, scaleXFrame10);
// Y 轴缩放
Keyframe scaleYFrame0 = Keyframe.ofFloat(0f, 1);
Keyframe scaleYFrame1 = Keyframe.ofFloat(0.1f, 1.2f);
Keyframe scaleYFrame9 = Keyframe.ofFloat(0.9f, 1.2f);
Keyframe scaleYFrame10 = Keyframe.ofFloat(1f, 1);
PropertyValuesHolder scaleYholder = PropertyValuesHolder.ofKeyframe("scaleY",
scaleYFrame0, scaleYFrame1, scaleYFrame9, scaleYFrame10);
Animator animator = ObjectAnimator.ofPropertyValuesHolder(mView,
frameHolder, scaleXholder, scaleYholder);
animator.setDuration(2000);
animator.start();